package echiquier;

import java.util.HashMap;
import java.util.HashSet;

import piece.Piece;
import core.AnnulationException;
import core.Coordonnees;
import core.Couleur;
import core.DeplacementException;
import core.JeuJoueurException;

/**
 * Classe échiquier, controleur d'un jeu d'échec
 * @author baptiste.andrini
 * @class gère le plateau d'échec, les déplacements et les prises
 */
public class Echiquier {
	/**
	 * Attributs
	 */
	private HashMap<Coordonnees, Case> plateau = null;
	private Jeu jeuBlanc;
	private Jeu jeuNoir;
	private Couleur joueurCourant;
	
	public final static Coordonnees maxCoordonnees = new Coordonnees(8,8);
	public final static Coordonnees minCoordonnees = new Coordonnees(1,1);

	/**
	 * Constructeur de la classe
	 * @brief Construit le plateau, les jeux, initialise le premier jour qui doit jouer au joueur blanc
	 */
	public Echiquier() {
		this.plateau = generationPlateau();
		this.jeuBlanc = new Jeu(Couleur.Blanc,plateau);
		this.jeuNoir = new Jeu(Couleur.Noir,plateau);
		this.joueurCourant = Couleur.Blanc;
	}
	
	/**
	 * Accesseur de la pièce de coordonnées données
	 * @param coordonnees
	 * @return pièce éventuellement présente aux coordonnées données
	 */
	public Piece getPieceCase(Coordonnees coordonnees) {
		if (coordonnees != null) {
			return this.plateau.get(coordonnees).getPiece();
		} else {
			return null;
		}
	}

	/**
	 * Accesseur du plateau
	 * @return Retourne le plateau HashMap(Coordonnees, Case) de l'échiquier.
	 */
	public HashMap<Coordonnees, Case> getPlateau() {
		return plateau;
	}
	
	/**
	 * Accesseur de la couleur courante
	 * @return
	 */
	public Couleur getJoueurCourant() {
		return this.joueurCourant;
	}
	
	/**
	 * Accesseur du jeu de couleur donné
	 * @param couleur
	 * @return
	 */
	private Jeu getJeu(Couleur couleur) {
		if (couleur == Couleur.Blanc)
			return jeuBlanc;
		else
			return jeuNoir;
	}
	
	/**
	 * Accesseur du jeu courant
	 * @return
	 */
	public Jeu getJeuCourant() {
		return getJeu(joueurCourant);
	}
	
	/**
	 * Accesseur du jeu adverse
	 * @return
	 */
	public Jeu getJeuAdverse() {
		return getJeu(Couleur.getOpposite(joueurCourant));
	}
	
	/**
	 * Accesseur des pièces déplaçables
	 * @return
	 * @throws RoiNonTrouvableException 
	 * @throws AnnulationException 
	 */
	public HashSet<Piece> getPieceDeplacable() {
		return Echiquier_deplacement_Utils.getPiecesDeplacable(this);
	}
	
	/**
	 * Accesseur des cases dans la zone de menace du jeu adverse
	 * @return
	 */
	public HashSet<Case> getZoneMenace() {
		return Echiquier_deplacement_Utils.getZoneMenace(getJeuAdverse(), plateau);
	}

	/**
	 * Accesseur de l'état du roi de couleur donnée
	 * @param coul
	 * @return
	 * @throws RoiNonTrouvableException
	 */
	public EtatRoi getEtatRoi(Couleur coul) {
		boolean deplacementValidePossible = !Echiquier_deplacement_Utils.getDeplacementsValides(this).isEmpty();
		boolean echecAuRoi = getZoneMenace().contains(getJeuCourant().getRoi().getCase());
		
		if (echecAuRoi && !deplacementValidePossible) {
			return EtatRoi.EchecEtMat; 
		} else if (echecAuRoi){
			return EtatRoi.Echec;
		} else if (!deplacementValidePossible) {
			return EtatRoi.Pat;
		} else {
			return EtatRoi.Sauf;
		}
	}
	
	/**
	 * Détermine si la case donnée est menacé ou pas
	 * @param c
	 * @return
	 */
	public boolean estMenace(Case c) {
		return getZoneMenace().contains(c);
	}
	
	/**
	 * Détermine si le roi du jeu courant est sous échec
	 * @param coul TODO
	 * @return
	 * @throws RoiNonTrouvableException 
	 */
	public boolean echecAuRoi(Couleur coul) {
		return (getEtatRoi(coul) == EtatRoi.Echec);
	}
	
	/**
	 * Mutateur pour changer le joueur courant
	 */
	private void changerJoueurCourant() {
		if (this.joueurCourant == Couleur.Blanc)
			this.joueurCourant = Couleur.Noir;
		else
			this.joueurCourant = Couleur.Blanc;
	}
	
	/**
	 * Méthode pour changer de tour
	 */
	public void nouveauTour() {
		//on change le joueur courant
		this.changerJoueurCourant();
	}

	/**
	 * Elemine une piece du jeu
	 * @param coordonnees
	 * @throws Exception 
	 */
	public void eliminePiece(Coordonnees coordonnees) {
		this.plateau.get(coordonnees).getPiece().elimine();
	}
	
	/**
	 * Génère un nouveau plateau.
	 * @return retourne le nouveau plateau.
	 */
	private  HashMap<Coordonnees, Case> generationPlateau() {
		HashMap<Coordonnees, Case> nouvPlat = new HashMap<Coordonnees, Case>();
		
		//pour chaque valeurs d'abcisses
		for (int y = minCoordonnees.y; y <= maxCoordonnees.y; y++) {
			//pour chaque valeurs d'ordonnées
			for (int x = minCoordonnees.x ; x <= maxCoordonnees.x ; x++ ) {
				//pour les coordonnées ainsi créée
				Coordonnees coord = new Coordonnees(x, y);
				//si la somme des coordonnées est paire
				if (x+y%2 == 0) {
					nouvPlat.put(coord, new Case(coord, Couleur.Noir));
				} else {
					nouvPlat.put(coord, new Case(coord, Couleur.Blanc));
				}
			}
		}
		return nouvPlat;
	}
	
	/**
	 * Déplace si possible la pièce donnée à la case donnée.
	 * @param p			pièce à déplacer
	 * @param casedest 	case de déstination
	 * @throws JeuJoueurException 
	 * @throws DeplacementException
	 */
	public void deplace(Piece p, Case casedest) throws DeplacementException, JeuJoueurException {
		if(p != null && p.getCouleur() == joueurCourant){
			HashSet<Case> casePossible;
			casePossible = Echiquier_deplacement_Utils.getDeplacementValide(p, this);
			if (casePossible.contains(casedest)) {
				Deplacement dep = new Deplacement(p, casedest.getCoordonnees());
				
				dep.deplace(this);
				
				if (dep.pieceElimine != null)
					System.out.println("Prise de "+dep.pieceElimine);
				
				System.out.println("Deplacement en " + dep.coordDestination);
				
				this.nouveauTour();
			} else {
				throw new DeplacementException();
			}
		}else if (p == null){
			throw new DeplacementException();
		}else{
			throw new JeuJoueurException();
		}
	}

	/**
	 * ToString
	 */
	public String toString() {
		String res = new String();
		for (Coordonnees c : this.plateau.keySet()) {
			if (!plateau.get(c).isFree()) {
				res.concat(c.toString()+ "\n");
			}
		}
		return res;
	}
}
